Update dependency phpseclib/phpseclib to ^3.0.51 [SECURITY]#80
Open
renovate[bot] wants to merge 1 commit intostagingfrom
Open
Update dependency phpseclib/phpseclib to ^3.0.51 [SECURITY]#80renovate[bot] wants to merge 1 commit intostagingfrom
renovate[bot] wants to merge 1 commit intostagingfrom
Conversation
| datasource | package | from | to | | ---------- | ------------------- | ------ | ------ | | packagist | phpseclib/phpseclib | 3.0.37 | 3.0.51 | Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3bb5c02 to
ae1e78d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
^3.0.37→^3.0.51GitHub Vulnerability Alerts
CVE-2026-32935
Impact
Those using AES in CBC mode may be susceptible to a padding oracle timing attack.
Patches
phpseclib/phpseclib@ccc21ae
Workarounds
Use AES in CTR, CFB or OFB modes
CVE-2026-40194
phpseclib SSH2: Variable-time comparison in HMAC verification
Summary
phpseclib\Net\SSH2::get_binary_packet()uses PHP's!=operator to compare a received SSH packet HMAC against the locally computed HMAC.!=on equal-length binary strings in PHP usesmemcmp(), which short-circuits on the first differing byte. This is a real variable-time comparison (CWE-208), proven by scaling benchmarks.The finding is Low severity (defense-in-depth), not Critical. Practical exploitation over the network is prevented by SSH's disconnect-on-MAC-failure behavior combined with per-connection session keys. The fix is a one-liner: replace
!=withhash_equals(), which the codebase already uses in 9 other places.phpseclib/phpseclibphpseclib/Net/SSH2.phpe819a163c): 3405 and 3410CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:Nmaster,3.0,2.0,1.0(all supported versions)Root cause
phpseclib/Net/SSH2.phplines 3399-3415 (master ate819a163c):Both
$hmac(read from the socket viaStrings::pop($raw, $this->hmac_size)at line 3348) and the computed hash are equal-length binary strings. PHP's!=operator on equal-length strings dispatches tozend_binary_strcmp()which internally callsmemcmp(). Modern libcmemcmpshort-circuits on the first differing byte, producing a timing signal that scales linearly with the number of matching leading bytes.The same bug exists on every supported branch
master(@e819a163c)phpseclib/Net/SSH2.php$hmac != $this->hmac_check->hash(...)3.0phpseclib/Net/SSH2.php$hmac != $this->hmac_check->hash(...)2.0phpseclib/Net/SSH2.php$hmac != $this->hmac_check->hash(...)1.0phpseclib/Net/SSH2.php$hmac != $this->hmac_check->hash(...)Verified with
git show <branch>:phpseclib/Net/SSH2.php.Reachability
The HMAC verification path at lines 3399-3415 is reached on every received SSH packet when the negotiated cipher is not AEAD — i.e., any of:
aes128-cbc,aes192-cbc,aes256-cbcaes128-ctr,aes192-ctr,aes256-ctr3des-cbc,3des-ctrblowfish-cbc,blowfish-ctrtwofish-*-cbc,twofish-*-ctrarcfour,arcfour128,arcfour256combined with any non-AEAD MAC (hmac-sha2-256, hmac-sha2-512, hmac-sha1, hmac-sha1-96, hmac-md5, hmac-md5-96, umac-64, umac-128, and their -etm variants — full list at
getSupportedMACAlgorithms()around line 4754).AEAD ciphers (
aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@​openssh.com) go through a different path (lines 3353-3381) and do not reach the!=comparison — their authentication tag is checked inside the AEAD implementation.$this->hmac_checkis set during key exchange atSSH2.php:1812:So any SSH2 client session negotiating a non-AEAD cipher exercises the vulnerable code path starting from the first post-KEX packet.
Contrast with existing code that does the right thing
hash_equals()is already used in 9 other places in the phpseclib codebase, proving the maintainer knows the pattern:The SSH2 MAC comparison is the one place it was missed.
Proof of concept
All scripts under
poc/. They prove three things:!=on equal-length binary strings IS variable-time and short-circuits.hash_equals()is constant-time over the same inputs.Crypt\Hashclass, the code path atSSH2.php:3405/3410leaks ~2-14 ns of signal per comparison.PoC 1: baseline measurement (
poc/01_verify_variable_time.php)Measures
!=vshash_equals()on 32-byte strings with first-byte vs last-byte mismatch. 500,000 iterations each, trimmed mean, Welch's t-test.Representative output (PHP 8.3.6 on Linux x86_64):
The 32-byte delta is small (~2 ns) and lives in the noise. This alone doesn't prove variable-time behavior. PoC 2 does.
PoC 2: scaling test (
poc/02_scaling_test.php)The decisive test. If
!=is truly constant-time, timing should not depend on mismatch position. If it short-circuits, timing should scale linearly with prefix length. Test strings from 32 bytes to 4096 bytes.This is monotone, linear scaling. Confirmed variable-time:
!=short-circuits. Per-byte delta ≈ 0.089 ns/byte (4096 bytes → ~284 ns → 284/4096 ≈ 0.069 ns/byte after accounting for the fixed call overhead).PoC 3: contrast with
hash_equals()(poc/03_hash_equals_scaling.php)Same test,
!=vshash_equals()on 1024-byte strings:!=monotonically increases with mismatch position.hash_equalsis flat; the 31 ns range is measurement jitter (sd ≈ 90 ns > range).Per-byte delta from this run: 91.62 / 1023 ≈ 0.089 ns/byte. Extrapolated to 32-byte HMAC: ~2.86 ns total signal between first-byte-diff and last-byte-diff.
PoC 4: end-to-end with phpseclib's Hash class (
poc/04_phpseclib_in_context.php)Uses
phpseclib4\Crypt\Hash(the exact class bound to$this->hmac_check) to compute a real HMAC-SHA-256, then executes the exact expression$hmac != $this->hmac_check->hash(...)from SSH2.php:3410:The 13.86 ns vs 2.86 ns variation between runs is within measurement variance — the signal is real and the sign matches every run (last > first for
!=, flat forhash_equals).Impact
Severity: Low (defense-in-depth). This is a real CWE-208 instance, but it is not a practical remote vulnerability.
Why exploitation over the network is infeasible
Signal is tiny. ~3-14 ns per HMAC compare. Network RTT jitter on a reasonable LAN is 100 µs (100,000 ns). On the internet it is 1-10 ms. The signal-to-noise ratio for a remote observer is ~1e-5 to 1e-7.
One measurement per connection. On every MAC failure,
SSH2.php:3406/3411callsdisconnect_helper(MAC_ERROR)and throws. The connection is torn down. A reconnect goes through a fresh key exchange, producing a new HMAC key. The HMAC over a fresh key is uncorrelated with the prior HMAC. An attacker cannot accumulate prefix-matching information across connections because there is no fixed target.MAC is over sequence-numbered data. Each packet's MAC input includes
$this->get_seq_no(line 3410) or a nonce (line 3404). Even within a single connection, replays are impossible — every MAC is bound to a distinct seq number. There is no stable oracle to probe.No adaptive probe. A Bleichenbacher/Lucky13-style attack needs tens of thousands of adaptive queries against a single secret. SSH's hard disconnect eliminates this primitive entirely.
Rough sample-count requirement. To distinguish a 3 ns signal from 1 ms jitter with high confidence you need roughly
(noise / signal)^2 ≈ (1e6 / 3)^2 ≈ 1e11independent samples. With one sample per connection (and each connection being against a fresh secret), this is unreachable on any practical scale.What remains real
hash_equals()in 9 other comparable places. The SSH2 MAC path is inconsistent with the rest of the codebase.memcmp()implementation changes, or if a future MAC algorithm uses longer digests, the signal grows linearly. A constant-time comparison eliminates the concern permanently.js/timing-attack, phpcs-security-audit) flag non-constant-time comparisons on secret values. A clean codebase passes those checks.CVSS breakdown
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N= 3.7 (Low)This is the same vector string used for CVE-2026-32935 (the recent AES-CBC padding oracle in the same library), differing only in Confidentiality (L vs H) because padding oracle actually recovers plaintext whereas this MAC timing does not enable any known plaintext recovery.
Fix
Two-line patch:
The same one-liner should be applied to
phpseclib/Net/SSH2.phpon3.0(lines 3741, 3746),2.0(line 3796), and1.0(line 3810).hash_equals()is a built-in PHP function available since PHP 5.6. phpseclib's minimum PHP version for all supported branches is well above that, so there is no compatibility concern — and indeed, as noted,hash_equals()is already used throughout the codebase for exactly this purpose.PoC 5: prefix-position byte-recovery test (
poc/05_prefix_position_test.php)The critical question: can an attacker recover the MAC byte-by-byte? Tests candidate MACs with 0, 1, 2, ..., 31 correct leading bytes using phpseclib's
Crypt\Hashclass. 500,000 iterations per position.No monotonic signal. The range is negative (wrong direction for an oracle), and the standard deviations (4-5 ns) are larger than any observed position-dependent delta. Byte-by-byte MAC recovery is not feasible even locally on 32-byte HMACs.
This conclusively rules out the "timing oracle for MAC forgery" escalation path.
Escalation angles investigated and ruled out
9 rounds of external review suggested various escalation paths. Every one is dead:
Devil's advocate review
Every objection I could think of, addressed:
"This is just a code smell. Show me a real exploit."
Acknowledged. The report does not claim remote exploitability. It is submitted as Low / defense-in-depth, consistent with how similar findings are treated in other cryptographic libraries (see e.g. OpenSSH commits replacing
memcmp()withtimingsafe_bcmpfor similar reasons)."PHP's
!=might not even short-circuit — you're just seeing noise."PoC 2 rules this out. Timing scales monotonically and nearly linearly with mismatch position across a 128x range of string lengths (32 bytes → 4096 bytes). That is not noise.
"SSH disconnects on MAC failure, so you only get one sample. Case closed."
Correct, and the report says so explicitly. This is why the severity is Low rather than High.
"There is no adaptive probe because session keys rotate per connection."
Correct, and the report says so explicitly. This is the primary reason the network attack is infeasible.
"Is this Lucky13-style? Does the MAC check happen after padding?"
No. MAC verification at
SSH2.php:3399-3414runs BEFORE any padding interpretation. Padding length is only read at line 3418, strictly after the MAC check. There is no unpadding oracle here. (Lucky13 in phpseclib's AES-CBC decrypt path is a separate, already-fixed issue — CVE-2026-32935.)"Maybe the
get_seq_noor nonce path somehow enables forgery."No. The seq number is deterministic (incremented by one per packet) and the nonce (for UMAC) is derived from it. Neither is attacker-controlled in any useful sense. The MAC key is the secret, and it is per-connection.
"Is this already reported?"
Searched published advisories (
gh api repos/phpseclib/phpseclib/security-advisories). Only CVE-2026-32935 is published, for a different code path (AES-CBC padding oracle after MAC verification). The SSH2 MAC comparison issue is not covered by any published advisory. The fix commit for CVE-2026-32935 (ccc21aef71eb170e9bf819b167e67d1fd9e6e788) did not touchSSH2.php:3405or:3410."Does the project's threat model even consider this in scope?"
SECURITY.md says "To report a security vulnerability, please use the Tidelift security contact." It does not exclude timing side-channels. The maintainer has already fixed constant-time comparison issues in other parts of the library (RSA, OAEP, AES-CBC padding) and uses
hash_equals()in 9 other places. This issue is consistent with the maintainer's existing security posture, and the fix is trivial."Is the Low severity appropriate? Could it be informational?"
Low is appropriate. "Informational" would be for something that is provably no-impact. This is a demonstrable CWE-208 instance on secret-dependent data. It scores 3.7 under CVSS v3.1 with AC:H and C:L. That is Low, not Informational. The recent CVE-2026-32935 is assigned for a timing-side-channel in the same file category and given CVSS 4.0 = 8.2 (their vector is more severe because padding oracle actually recovers plaintext). This one would score lower than that, which matches the Low designation.
"Would a maintainer just close this?"
Unlikely. The fix is a one-liner, there is no behavioral change, it aligns with existing code style in the same codebase, and it silences future audit findings. The maintainer has a track record of accepting similar hardening patches. If they close it, the reason would likely be "we already know, not worth a CVE" — which would still leave the code fixed, which is the goal.
Suggested reporting path
GitHub PVR is enabled for phpseclib/phpseclib (
{"enabled":true}). SECURITY.md says to use the Tidelift contact, but GitHub PVR is a reasonable alternative and lets the maintainer decide whether to coordinate with Tidelift. Either path is acceptable.Given the Low severity, this could also be filed as a public pull request rather than a security advisory. That would be faster for everyone and avoids using the private disclosure channel for a hardening fix.
Files
poc/01_verify_variable_time.php— baseline 32-byte timing measurementpoc/02_scaling_test.php— decisive scaling test across string lengthspoc/03_hash_equals_scaling.php—!=vshash_equals()comparisonpoc/04_phpseclib_in_context.php— end-to-end repro using phpseclib'sCrypt\Hashnotes.md— research notes, dead ends, escalation angles consideredprior-work.md— prior work search resultsstatus.json— machine-readable statusKoda Reef
Release Notes
phpseclib/phpseclib (phpseclib/phpseclib)
v3.0.51Compare Source
v3.0.50Compare Source
v3.0.49Compare Source
v3.0.48Compare Source
v3.0.47Compare Source
v3.0.46Compare Source
v3.0.45Compare Source
v3.0.44Compare Source
v3.0.43Compare Source
v3.0.42Compare Source
v3.0.41Compare Source
v3.0.40Compare Source
v3.0.39Compare Source
v3.0.38Compare Source
Configuration
📅 Schedule: (in timezone UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
Read more information about the use of Renovate Bot within Laminas.